﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using OpenQuarters.WebQuarters.Core;
using System.Web;
using System.Web.UI;
using OpenQuarters.WebQuarters.Utils;
using System.Web.Mvc;
using OpenQuarters.EntityBase;
using System.Data.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Web.Mvc.Html;
using System.IO;

namespace OpenQuarters.WebQuarters.Core
{
    public class ItemLister<T> : ViewUserControl<T> where T : class, IItemListable
    {
        #region Properties

        private ItemListerConfiguration configuration = new ItemListerConfiguration();

        public ItemListerConfiguration Configuration
        {
            get { return configuration; }
            set { configuration = value; }
        }

        //private T itemType = null;

        //public T ItemType
        //{
        //    get { return itemType; }
        //    set { itemType = value; }
        //}

        #endregion

        #region Constructors

        public ItemLister()
            : base()
        {
        }
        
        public ItemLister(ItemListerConfiguration config, T itemType)
            : base()
        {
            this.Configuration = config;
            //this.ItemType = itemType;
        }

        #endregion

        #region Methods
        
        public ItemListResponse BuildItemList()
        {
            ItemListResponse response = new ItemListResponse();
            ParameterExpression fExpParameter = Expression.Parameter(typeof(T), "i");
            Expression fExpFilter = null;

            response.Pagination = new PaginationData();

            // Filter items
            foreach (var f in Configuration.Filters)
            {
                object objValue = null;
                try { objValue = f.GetValueAsObject(typeof(T)); }
                catch { continue; }

                try
                {
                    string fieldName = f.Field;
                    var attrs = typeof(T).GetProperty(f.Field).GetCustomAttributes(typeof(PropertyConfigurationAttribute), true).Cast<PropertyConfigurationAttribute>().ToList();
                    if (attrs.Count > 0 && attrs[0].SourceField.Length > 0)
                    {
                        fieldName = attrs[0].SourceField;
                    }
                    Expression fExpLeft = Expression.Property(fExpParameter, fieldName);
                    Expression fExpRight = null;
                    Expression fExp = null;
                    switch (f.OperatorName)
                    {
                        case FilterOperator.Contains:
                        case FilterOperator.Does_not_contain:
                            MethodInfo containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                            fExp = Expression.Call(
                                fExpLeft,
                                containsMethod,
                                new Expression[] { Expression.Constant(objValue, typeof(string)) });

                            if (f.OperatorName == FilterOperator.Does_not_contain)
                            {
                                fExp = Expression.Not(fExp);
                            }
                            break;
                        case FilterOperator.In:
                            string[] values = objValue.ToString().Split(',');
                            List<string> queryable = values.ToList<string>();

                            fExpLeft = Expression.Constant(queryable, typeof(List<string>));

                            MethodInfo containsMethod2 = typeof(List<string>).GetMethod("Contains");

                            fExp = Expression.Call(
                                fExpLeft,
                                containsMethod2,
                                new Expression[] { Expression.Property(fExpParameter, fieldName) });

                            break;
                        default:
                            fExpRight = Expression.Constant(objValue);
                            break;
                    }

                    switch (f.OperatorName)
                    {
                        case FilterOperator.Equals:
                            fExp = Expression.Equal(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Does_not_equal:
                            fExp = Expression.NotEqual(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Less_than:
                            fExp = Expression.LessThan(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.Less_than_or_equal_to:
                            fExp = Expression.LessThanOrEqual(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.More_than:
                            fExp = Expression.GreaterThan(fExpLeft, fExpRight);
                            break;
                        case FilterOperator.More_than_or_equal_to:
                            fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight);
                            break;
                    }

                    if (fExp != null)
                    {
                        if (fExpFilter == null)
                        {
                            fExpFilter = fExp;
                        }
                        else
                        {
                            fExpFilter = Expression.AndAlso(fExpFilter, fExp);
                        }
                    }
                }
                catch { continue; }
            }

            var tbl = Activator.CreateInstance<T>().GetQueryableList();
            List<T> items = new List<T>();

            MethodCallExpression expFull = null;

            if (fExpFilter != null)
            {
                expFull = Expression.Call(
                    typeof(Queryable),
                    "Where",
                    new Type[] { typeof(T) },
                    tbl.Expression,
                    Expression.Lambda<Func<T, bool>>(fExpFilter, new ParameterExpression[] { fExpParameter }));
            }

            // Ordering
            bool bOrderedFirst = false;
            foreach (var o in Configuration.Ordering)
            {
                try
                {
                    string fieldName = o.Field;
                    var attrs = typeof(T).GetProperty(o.Field).GetCustomAttributes(typeof(PropertyConfigurationAttribute), true).Cast<PropertyConfigurationAttribute>().ToList();
                    if (attrs.Count > 0 && attrs[0].SourceField.Length > 0)
                    {
                        fieldName = attrs[0].SourceField;
                    }

                    Type resultType = null;
                    LambdaExpression orderByLambda = Utils.LinqUtils.GenerateSelector<T>(fieldName, out resultType);

                    expFull = Expression.Call(
                        typeof(Queryable),
                        (bOrderedFirst ? "ThenBy" : "OrderBy") + (o.Operator == "desc" ? "Descending" : ""),
                        new Type[] { typeof(T), resultType },
                        expFull == null ? tbl.Expression : expFull,
                        Expression.Quote(orderByLambda));

                    bOrderedFirst = true;
                }
                catch { continue; }
            }

            if (!string.IsNullOrEmpty(this.Configuration.Method))
            {
                //itemTotal = ((List<T>)GeneralUtils.CallMethod(this.Configuration.Method)).Count();

                //TODO: Find a way to call the filter expression on the custom method 

                tbl = ((IQueryable<T>)GeneralUtils.CallMethod(this.Configuration.Method)).AsQueryable<T>();

                //items = ((List<T>)GeneralUtils.CallMethod(this.Configuration.Method)).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).ToList();
            }
            else
            {
                //itemTotal = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Count();

                //items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).ToList();
            }

            response.Pagination.CurrentPage = configuration.Current_Page;
            response.Pagination.ItemsPerPage = configuration.Items_Per_Page;
            response.Pagination.PaginationMode = configuration.Pagination_Mode;
            response.Pagination.TotalItemCount = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Count();

            if (Configuration.Current_Page == 0 && Configuration.Items_Per_Page == 0)
                response.Items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Cast<object>().ToList();
            else
                response.Items = tbl.Provider.CreateQuery<T>(expFull == null ? tbl.Expression : expFull).Skip((Configuration.Current_Page - 1) * Configuration.Items_Per_Page).Take(Configuration.Items_Per_Page).Cast<object>().ToList();

            return response;
        }

        private string getItemJSONField(T item)
        {
            string elementID = item.GetType().FullName.Replace(".", "_") + "_" + item.GetIDAsString();
            return ContextItem.GetItem("cmsItemListAdded_" + elementID, () => false) ? "" : @"<textarea style=""display:none;"" id=""" + elementID + @""">" + item.ToJSON().Base64Encode() + "</textarea>";
        }

        #endregion
    }
}
